/* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */

//#include "table_cache.h"
#include "sql_test.h" // lock_descriptions[]


/**
  Container for all table cache instances in the system.
*/
Table_cache_manager table_cache_manager;


#ifdef HAVE_PSI_INTERFACE
PSI_mutex_key Table_cache::m_lock_key;
PSI_mutex_info Table_cache::m_mutex_keys[] = {
    { &m_lock_key, "LOCK_table_cache", 0}
};
#endif


extern "C" uchar *table_cache_key(const uchar *record,
                                  size_t *length,
                                  my_bool not_used __attribute__((unused)))
{
    TABLE_SHARE *share = ((Table_cache_element *)record)->get_share();
    *length = share->table_cache_key.length;
    return (uchar *) share->table_cache_key.str;
}


extern "C" void table_cache_free_entry(Table_cache_element *element)
{
    delete element;
}


/**
  Initialize instance of table cache.

  @retval false - success.
  @retval true  - failure.
*/

bool Table_cache::init()
{
    mysql_mutex_init(m_lock_key, &m_lock, MY_MUTEX_INIT_FAST);
    m_unused_tables = NULL;
    m_table_count = 0;

    if (my_hash_init(&m_cache, &my_charset_bin,
                     table_cache_size_per_instance, 0, 0,
                     table_cache_key, (my_hash_free_key) table_cache_free_entry,
                     0)) {
        mysql_mutex_destroy(&m_lock);
        return true;
    }

    return false;
}


/** Destroy instance of table cache. */

void Table_cache::destroy()
{
    my_hash_free(&m_cache);
    mysql_mutex_destroy(&m_lock);
}


/** Init P_S instrumentation key for mutex protecting Table_cache instance. */

void Table_cache::init_psi_keys()
{
#ifdef HAVE_PSI_INTERFACE
    mysql_mutex_register("sql", m_mutex_keys, array_elements(m_mutex_keys));
#endif
}


#ifdef EXTRA_DEBUG
void Table_cache::check_unused()
{
    uint count = 0;

    if (m_unused_tables != NULL) {
        TABLE *cur_link = m_unused_tables;
        TABLE *start_link = m_unused_tables;

        do {
            if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next) {
                DBUG_PRINT("error", ("Unused_links aren't linked properly"));
                return;
            }
        } while (count++ < m_table_count &&
                 (cur_link = cur_link->next) != start_link);

        if (cur_link != start_link)
            DBUG_PRINT("error", ("Unused_links aren't connected"));
    }

    for (uint idx = 0; idx < m_cache.records; idx++) {
        Table_cache_element *el =
            (Table_cache_element *) my_hash_element(&m_cache, idx);
        Table_cache_element::TABLE_list::Iterator it(el->free_tables);
        TABLE *entry;

        while ((entry = it++)) {
            /* We must not have TABLEs in the free list that have their file closed. */
            DBUG_ASSERT(entry->db_stat && entry->file);
            /* Merge children should be detached from a merge parent */
            DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));

            if (entry->in_use)
                DBUG_PRINT("error", ("Used table is in share's list of unused tables"));

            count--;
        }

        it.init(el->used_tables);

        while ((entry = it++)) {
            if (!entry->in_use)
                DBUG_PRINT("error", ("Unused table is in share's list of used tables"));
        }
    }

    if (count != 0)
        DBUG_PRINT("error", ("Unused_links doesn't match open_cache: diff: %d",
                             count));
}
#endif


/** Free all unused TABLE objects in the table cache. */

void Table_cache::free_all_unused_tables()
{
    assert_owner();

    while (m_unused_tables) {
        TABLE *table_to_free = m_unused_tables;
        remove_table(table_to_free);
        intern_close_table(table_to_free);
    }
}


#ifndef DBUG_OFF
/**
  Print debug information for the contents of the table cache.
*/

void Table_cache::print_tables()
{
    uint unused = 0;
    uint count = 0;
    compile_time_assert(TL_WRITE_ONLY + 1 == array_elements(lock_descriptions));

    for (uint idx = 0; idx < m_cache.records; idx++) {
        Table_cache_element *el =
            (Table_cache_element *) my_hash_element(&m_cache, idx);
        Table_cache_element::TABLE_list::Iterator it(el->used_tables);
        TABLE *entry;

        while ((entry = it++)) {
            printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
                   entry->s->db.str, entry->s->table_name.str, entry->s->version,
                   entry->in_use->thread_id, entry->db_stat ? 1 : 0,
                   lock_descriptions[(int)entry->reginfo.lock_type]);
        }

        it.init(el->free_tables);

        while ((entry = it++)) {
            unused++;
            printf("%-14.14s %-32s%6ld%8ld%6d  %s\n",
                   entry->s->db.str, entry->s->table_name.str, entry->s->version,
                   0L, entry->db_stat ? 1 : 0, "Not in use");
        }
    }

    if (m_unused_tables != NULL) {
        TABLE *start_link = m_unused_tables;
        TABLE *lnk = m_unused_tables;

        do {
            if (lnk != lnk->next->prev || lnk != lnk->prev->next) {
                printf("unused_links isn't linked properly\n");
                return;
            }
        } while (count++ < m_table_count && (lnk = lnk->next) != start_link);

        if (lnk != start_link)
            printf("Unused_links aren't connected\n");
    }

    if (count != unused)
        printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
               unused);
}
#endif


/**
  Initialize all instances of table cache to be used by server.

  @retval false - success.
  @retval true  - failure.
*/

bool Table_cache_manager::init()
{
    Table_cache::init_psi_keys();

    for (uint i = 0; i < table_cache_instances; i++) {
        if (m_table_cache[i].init()) {
            for (uint j = 0; j < i; j++)
                m_table_cache[i].destroy();

            return true;
        }
    }

    return false;
}


/** Destroy all instances of table cache which were used by server. */

void Table_cache_manager::destroy()
{
    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].destroy();
}


/**
  Get total number of used and unused TABLE objects in all table caches.

  @note Doesn't require acquisition of table cache locks if inexact number
        of tables is acceptable.
*/

uint Table_cache_manager::cached_tables()
{
    uint result = 0;

    for (uint i = 0; i < table_cache_instances; i++)
        result += m_table_cache[i].cached_tables();

    return result;
}


/**
  Acquire locks on all instances of table cache and table definition
  cache (i.e. LOCK_open).
*/

void Table_cache_manager::lock_all_and_tdc()
{
    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].lock();

    mysql_mutex_lock(&LOCK_open);
}


/**
  Release locks on all instances of table cache and table definition
  cache.
*/

void Table_cache_manager::unlock_all_and_tdc()
{
    mysql_mutex_unlock(&LOCK_open);

    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].unlock();
}


/**
  Assert that caller owns locks on all instances of table cache.
*/

void Table_cache_manager::assert_owner_all()
{
    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].assert_owner();
}


/**
  Assert that caller owns locks on all instances of table cache
  and table definition cache.
*/

void Table_cache_manager::assert_owner_all_and_tdc()
{
    assert_owner_all();
    mysql_mutex_assert_owner(&LOCK_open);
}


/**
   Remove and free all or some (depending on parameter) TABLE objects
   for the table from all table cache instances.

   @param  thd          Thread context
   @param  remove_type  Type of removal. @sa tdc_remove_table().
   @param  share        TABLE_SHARE for the table to be removed.

   @note Caller should own LOCK_open and locks on all table cache
         instances.
*/
void Table_cache_manager::free_table(THD *thd,
                                     enum_tdc_remove_table_type remove_type,
                                     TABLE_SHARE *share)
{
    Table_cache_element *cache_el[MAX_TABLE_CACHES];
    assert_owner_all_and_tdc();
    /*
      Freeing last TABLE instance for the share will destroy the share
      and corresponding TABLE_SHARE::cache_element[] array. To make
      iteration over this array safe, even when share is destroyed in
      the middle of iteration, we create copy of this array on the stack
      and iterate over it.
    */
    memcpy(&cache_el, share->cache_element,
           table_cache_instances * sizeof(Table_cache_element *));

    for (uint i = 0; i < table_cache_instances; i++) {
        if (cache_el[i]) {
            Table_cache_element::TABLE_list::Iterator it(cache_el[i]->free_tables);
            TABLE *table;
#ifndef DBUG_OFF

            if (remove_type == TDC_RT_REMOVE_ALL)
                DBUG_ASSERT(cache_el[i]->used_tables.is_empty());
            else if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
                     remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE) {
                Table_cache_element::TABLE_list::Iterator it2(cache_el[i]->used_tables);

                while ((table = it2++)) {
                    if (table->in_use != thd)
                        DBUG_ASSERT(0);
                }
            }

#endif

            while ((table = it++)) {
                m_table_cache[i].remove_table(table);
                intern_close_table(table);
            }
        }
    }
}


/** Free all unused TABLE objects in all table cache instances. */

void Table_cache_manager::free_all_unused_tables()
{
    assert_owner_all_and_tdc();

    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].free_all_unused_tables();
}


#ifndef DBUG_OFF
/**
  Print debug information for the contents of all table cache instances.
*/

void Table_cache_manager::print_tables()
{
    puts("DB             Table                            Version  Thread  Open  Lock");

    for (uint i = 0; i < table_cache_instances; i++)
        m_table_cache[i].print_tables();
}
#endif

